//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/builtins/builtins.h"
#include "sagittarius/builtins/modules/sbml.h"
#include "sagittarius/io/io.h"
#include "sagittarius/backend/llvm/support.h"
#include <string.h>
#include <malloc.h>
#include <string>
#include <sstream>

#if SAGITTARIUS_ENABLE_LIBSBML

#include "sbml/SBMLTypes.h"

namespace Sagittarius {
    class sgSBML_NetworkWriter {
        public:
            sgSBML_NetworkWriter(sg_network_val* _net)
                : net(_net) {}
            
            void writeNetwork(Model* model, Compartment* comp) {
                sg_list_iter_val* iter = sg_list_get_iter(net->rules);
                
                sg_value* val;
                sg_reaction_rule_val* rule = sg_cast_to_reaction_rule(sg_list_iter_deref(iter));
                
                int n=1;
                
                while(rule) {
                    writeRule(rule, model, comp, n++);
                    
                    sg_list_iter_next(iter);
                    val = sg_list_iter_deref(iter);
                    if(val)
                        rule = sg_cast_to_reaction_rule(val);
                    else rule = NULL;
                }
            }
            
            void writeRule(sg_reaction_rule_val* rule, Model* model, Compartment* comp, int n) {
                AT(rule->t == (sg_type*)sg_global_session()->bt->rxn_rule, "Unexpected type");
                Reaction* rxn = model->createReaction();
                // TODO: add support in the language for naming reactions
                
                #if SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_MINGW
                std::stringstream ss;
                ss << n;
                rxn->setId("reaction" + ss.str());
                #else
                rxn->setId("reaction" + std::to_string(n)); // C++11: to_string
                #endif
                rxn->setReversible(false);
                
                AN(rule->rxn, "Empty reaction");
                writeRxnNode(rule->rxn, model, rxn, comp, RXN_TYPE_DEFAULT);
                
                KineticLaw* kine = rxn->createKineticLaw();
                AT(kine->setFormula(sg_node_str(rule->rate, sg_getOpPrec(rule->rate->type), 0)) == LIBSBML_OPERATION_SUCCESS, "Failed to set kinetic law");
                
                processRateLawExp(rule->rate, model);
            }
            
            enum RxnNodeType {
                RXN_TYPE_DEFAULT,
                RXN_TYPE_REACTANTS,
                RXN_TYPE_PRODUCTS
            };
            
            void writeRxnNode(sg_node_val* node, Model* model, Reaction* rxn, Compartment* comp, RxnNodeType rt) {
                AT(node->t == (sg_type*)sg_global_session()->bt->node, "Unexpected type");
                
                switch(node->type) {
                    case PAST_NODE_TYPE_SYMBOL: {
                        sg_name_val* name = node->name;
                        AN(name, "Expected name");
                        std::string str(sg_name_from_sym(name->s, sg_global_session()->st));
                        
                        switch(name->sym_tp) {
                            case SG_SYMTP_NORMAL:
                                break;
                            case SG_SYMTP_SPECIES:
                                //str += "[SPECIES]";
                                break;
                            case SG_SYMTP_RATE_LAW_CONST:
                                //str += "[INFERRED]";
                                break;
                            default:
                                AN(0, "Unknown symbol type");
                        }
                        if(!model->getSpecies(str)) {
                            Species* spec = model->createSpecies();
                            spec->setId(str);
                            spec->setBoundaryCondition(false);
                            spec->setInitialConcentration(0.);
                            spec->setCompartment(comp->getId());
                        }
                        SpeciesReference* sref=NULL;
                        if(rt == RXN_TYPE_REACTANTS)
                            sref = rxn->createReactant();
                        else if(rt == RXN_TYPE_PRODUCTS)
                            sref = rxn->createProduct();
                        sref->setSpecies(str);
                        sref->setStoichiometry(1.);
                        return;
                    }
                    case PAST_NODE_TYPE_ADD: {
                        AN(node->a, "Empty lhs");
                        AN(node->b, "Empty rhs");
                        
                        writeRxnNode(node->a, model, rxn, comp, rt);
                        writeRxnNode(node->b, model, rxn, comp, rt);
                        return;
                    }
                    case PAST_NODE_TYPE_RXN: {
                        AN(node->a, "Empty lhs");
                        AN(node->b, "Empty rhs");
                        
                        writeRxnNode(node->a, model, rxn, comp, RXN_TYPE_REACTANTS);
                        writeRxnNode(node->b, model, rxn, comp, RXN_TYPE_PRODUCTS);
                        return;
                    }
                    default:
                        AN(0, "Unkown node type");
                }
            }
            
            void processRateLawExp(sg_node_val* node, Model* model) {
                AT(node->t == (sg_type*)sg_global_session()->bt->node, "Unexpected type");
                
                switch(node->type) {
                    case PAST_NODE_TYPE_SYMBOL: {
                        sg_name_val* name = node->name;
                        AN(name, "Expected name");
                        std::string str(sg_name_from_sym(name->s, sg_global_session()->st));
                        
                        int make_param=0;
                        
                        switch(name->sym_tp) {
                            case SG_SYMTP_NORMAL:
                                make_param = 1;
                                break;
                            case SG_SYMTP_SPECIES:
                                break;
                            case SG_SYMTP_RATE_LAW_CONST:
                                make_param = 1;
                                break;
                            default:
                                AN(0, "Unknown symbol type");
                        }
                        if(make_param & !model->getParameter(str)) {
                            Parameter* param = model->createParameter();
                            param->setId(str);
                            if(node->obj)
                                param->setValue(sg_get_real_scalar(node->obj));
                        }
                        return;
                    }
                    case PAST_NODE_TYPE_ADD: {
                        AN(node->a, "Empty lhs");
                        AN(node->b, "Empty rhs");
                        
                        processRateLawExp(node->a, model);
                        processRateLawExp(node->b, model);
                        return;
                    }
                    case PAST_NODE_TYPE_SUB: {
                        AN(node->a, "Empty lhs");
                        AN(node->b, "Empty rhs");
                        
                        processRateLawExp(node->a, model);
                        processRateLawExp(node->b, model);
                        return;
                    }
                    case PAST_NODE_TYPE_MUL: {
                        AN(node->a, "Empty lhs");
                        AN(node->b, "Empty rhs");
                        
                        processRateLawExp(node->a, model);
                        processRateLawExp(node->b, model);
                        return;
                    }
                    case PAST_NODE_TYPE_DIV: {
                        AN(node->a, "Empty lhs");
                        AN(node->b, "Empty rhs");
                        
                        processRateLawExp(node->a, model);
                        processRateLawExp(node->b, model);
                        return;
                    }
                    default:
                        AN(0, "Unkown node type");
                }
            }
            
        protected:
            sg_network_val* net;
    };
}

using namespace Sagittarius;

void sg_mod_sbml_write(sg_network_val* net, sg_str_val* str) {
    SG_WRITE(2, "sg_mod_sbml_write\n");
    SBMLWriter writer;
    SBMLDocument* doc = new SBMLDocument();
    
    Model* model = doc->createModel("model");
    
    // compartment
    Compartment* comp = model->createCompartment();
    comp->setId("compartment");
    comp->setSize(1.);
    
    // write network
    sgSBML_NetworkWriter nw(net);
    nw.writeNetwork(model, comp);
    
    writer.writeSBML(doc, sg_lex_buf2stdstrl(str->w, str->n));
    
    delete doc;
}

#endif // SAGITTARIUS_ENABLE_LIBSBML
